package nf.graph;

import java.util.Collection;
import java.util.Set;
import java.util.HashSet;

/**
 * Represents the difference between two graphs.
 */
public class Delta
{
	private Set<Edge> additions;
	private Set<Edge> subtractions;
	
	private boolean frozen;
	
	public static final Delta EMPTY = new Delta().freeze();
	
	public Delta()
	{
		this(new HashSet<Edge>(), new HashSet<Edge>());
	}
	
	public Delta(Collection<Edge> additions,
		Collection<Edge> subtractions)
	{
		this(new HashSet<Edge>(additions), new HashSet<Edge>(subtractions));
	}
	
	public Delta(Delta other)
	{
		this(new HashSet<Edge>(other.getAdditions()),
			new HashSet<Edge>(other.getSubtractions()));
	}
	
	private Delta(Set<Edge> additions, Set<Edge> subtractions)
	{
		this.additions = additions;
		this.subtractions = subtractions;
		this.frozen = false;
	}
	
	public Delta freeze()
	{
		this.frozen = true;
		return this;
	}
	
	public Collection<Edge> getAdditions()
	{
		return additions;
	}
	
	public Collection<Edge> getSubtractions()
	{
		return subtractions;
	}
	
	public boolean isEmpty()
	{
		return additions.isEmpty() && subtractions.isEmpty();
	}
	
	private void verifyThawed()
	{
		if (frozen) {
			throw new IllegalStateException("Cannot modify a delta that is " +
				"frozen.");
		}
	}
	
	public Delta add(Edge edge)
	{
		verifyThawed();
		subtractions.remove(edge);
		additions.add(edge);
		return this;
	}
	
	public Delta add(Node head, Node tail)
	{
		return add(new Edge(head, tail));
	}
	
	public Delta subtract(Edge edge)
	{
		verifyThawed();
		additions.remove(edge);
		subtractions.add(edge);
		return this;
	}
	
	public Delta subtract(Node head, Node tail)
	{
		return subtract(new Edge(head, tail));
	}
	
	public Delta combine(Delta other)
	{
		verifyThawed();
		
		additions.removeAll(other.getSubtractions());
		subtractions.removeAll(other.getAdditions());
		
		additions.addAll(other.getAdditions());
		subtractions.addAll(other.getSubtractions());
		
		return this;
	}
	
	public Delta invert()
	{
		Delta inverted = new Delta(
			(Collection<Edge>) subtractions,
			(Collection<Edge>) additions
		);
		
		return (frozen)
			? inverted.freeze()
			: inverted;
	}
	
	/**
	 * Applies the Delta to the given graph.
	 * @param graph the graph on which the Delta should be applied
	 * @return the changed graph
	 */
	public Graph applyTo(Graph graph)
	{
		for (Edge addition : additions) {
			graph.connect(addition.getHead(), addition.getTail());
		}
		
		for (Edge subtraction : subtractions) {
			graph.disconnect(subtraction.getHead(), subtraction.getTail());
		}
	
		return graph;
	}
	
	/**
	 * Applies the opposite of the Delta to the given graph.
	 * @param graph the graph on which the opposite of the Delta should be
	 * applied
	 * @return the changed graph
	 */
	public Graph undo(Graph graph)
	{
		return invert().applyTo(graph);
	}
	
	public String toString()
	{
		StringBuilder b = new StringBuilder("<" + getClass().getName());
		
		for (Edge edge : additions) {
			b.append(" +(");
			b.append(edge.getHead().getName());
			b.append(", ");
			b.append(edge.getTail().getName());
			b.append(")");
		}
		
		for (Edge edge : subtractions) {
			b.append(" -(");
			b.append(edge.getHead().getName());
			b.append(", ");
			b.append(edge.getTail().getName());
			b.append(")");
		}
		
		b.append(">");
		return b.toString();
	}
}
